package netty.test1Base;

import org.junit.Test;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Created by majian8 on 2018/12/3.
 * <p>
 * 1，buffer 的 rewind()方法Buffer.rewind()将position设回0，所以你可以重读Buffer中的所有数据。
 * limit保持不变，仍然表示能从Buffer中读取多少个元素（byte、char等）。
 * 2，clear()与compact()方法一旦读完Buffer中的数据，需要让Buffer准备好再次被写入。
 * 可以通过clear()或compact()方法来完成。如果调用的是clear()方法，position将被设回0，
 * limit被设置成 capacity的值。换句话说，Buffer 被清空了。Buffer中的数据并未清除，
 * 只是这些标记告诉我们可以从哪里开始往Buffer里写数据。如果Buffer中有一些未读的数据，
 * 调用clear()方法，数据将“被遗忘”，意味着不再有任何标记会告诉你哪些数据被读过，哪些还没有。
 * 如果Buffer中仍有未读的数据，且后续还需要这些数据，但是此时想要先先写些数据，
 * 那么使用compact()方法。compact()方法将所有未读的数据拷贝到Buffer起始处。
 * 然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样，设置成capacity。
 * 现在Buffer准备好写数据了，但是不会覆盖未读的数据
 */
public class NioTest {

    /**
     * buffer 读写操作,每个channel都对应一个 buffer 每个selector对应一个线程
     * 一个线程对应多个 channel(一个selector上可以注册多个 channel)
     * 程序切换到哪个channel是有事件决定的，selector 会根据不同的事件，在各个
     * 通道上切换
     */
    @Test
    public void test1() {
        //1,存放大小为5 可以存放 5 个int
        IntBuffer buf1 = IntBuffer.allocate(5);
        buf1.put(10);
        buf1.put(11);
        buf1.put(12);
        buf1.put(13);
        buf1.put(14);
        System.out.println(buf1.capacity());

        //2,取数据
        buf1.flip();//读写切换
        while (buf1.hasRemaining()) {
            System.out.println(buf1.get());
        }

    }


    /**
     * buffer 的读写
     */
    @Test
    public void test2() {
        try {
            RandomAccessFile rf = new RandomAccessFile("d:/a.txt", "rw");
            FileChannel fileChannel = rf.getChannel();
            ByteBuffer buf = ByteBuffer.allocate(20);
            try {
                //1,从channel 中读取数据到 buffer中(和io有点像)
                int len = 0;
                //2,这步其实是写入数据到buf
                // (写buf 有两种方式，第一张就是下面的，从channel中写到buf中，第二种就是，通过buf 本身写入buf （buf的put方法）)
                //同理，读buf 也有两种方式，（第一种，从buf读到channel  通过channel.write(buf),第二种，通过buf 的get 方法）
                while ((len = fileChannel.read(buf)) != -1) {

                    //3,flip切换缓冲区的读写模式,调用flip()方法会将position设回0，并将limit设置成之前position的值
                    buf.flip();
                    while (buf.hasRemaining()) {
                        System.out.println((char) buf.get());//开始读 buf
                    }
                    //4,读完了数据就要清空缓冲区buf，以便缓冲区能再次被写入
                    //有两种方式能清空缓冲区：调用clear()或compact()方法。clear()方法会清空整个缓冲区。
                    // compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处，
                    // 新写入的数据将放到缓冲区未读数据的后面
                    buf.clear();
                }
                rf.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }


    /**
     * 通过 FileOutputStream  得到fileChannel
     */
    @Test
    public void test211() throws IOException {
        //1,获取 fileChannel
        String str = "this is test file str";
        FileOutputStream fos = new FileOutputStream("d://a.txt");
        FileChannel fileChannel = fos.getChannel();

        //2,创建buf (先把文本写入buf  再把buf写入channel)
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(str.getBytes());

        //3,上面buf写完了，开始反转buf,然后读取buf 写入 channel
        buffer.flip();
        fileChannel.write(buffer);
        fileChannel.force(true);
        //关闭了顶层的流，底层的也会关闭
        fos.close();


    }

    /**
     * 从 fileInputStream 中获取channel
     *
     * @throws IOException
     */
    @Test
    public void test22() throws IOException {
        File file = new File("d:/a.txt");
        FileInputStream in = new FileInputStream(file);

        FileChannel fileChannel = in.getChannel();
        ByteBuffer buf = ByteBuffer.allocate((int) file.length());

        //3,从chanel 读数据到 buf
        fileChannel.read(buf);
        //4,将字节转为 字符
        System.out.println(new String(buf.array()));

        in.close();
    }

    /**
     * 用一个buffer 完成文件的输入输出
     *
     * @throws IOException
     */
    @Test
    public void test23() throws IOException {
        //1.1输入文件
        File file1 = new File("d:/c.txt");
        FileInputStream in = new FileInputStream(file1);
        FileChannel inChannel = in.getChannel();


        //2.1
        File file2 = new File("d:/d.txt");
        FileOutputStream out = new FileOutputStream(file2);
        FileChannel outChanel = out.getChannel();

        //3.1 创建buffer
        ByteBuffer buf = ByteBuffer.allocate(512);

        while (true) {
            //这里有一个重要的动作，复位 buf.clear();,否则的话，如果数据量小的话，write之后，postion和limit的值相等
            //再读的话。就会返回0 （没法往里加数据了） clear() 方法相当于把 postion和limit 置为开始创建buf 时设置的值
            buf.clear();
            int read = inChannel.read(buf);
            if (read == -1) {
                break;
            }
            buf.flip();
            outChanel.write(buf);
        }

        in.close();
        out.close();
    }


    /**
     * 通道之间的数据传输
     * transferFrom
     * 和 transferTo
     * <p>
     * 在Java NIO中，如果两个通道中有一个是FileChannel，
     * 那你可以直接将数据从一个channel传输到另外一个channel
     */
    @Test
    public void test24() throws IOException {
        //1,transfer from ()  从 源 channel 传到 FileChannel
        RandomAccessFile fromFile = new RandomAccessFile("d:/a.txt", "rw");
        FileChannel sourceChannel = fromFile.getChannel();

        RandomAccessFile toFile = new RandomAccessFile("d:/b.txt", "rw");
        FileChannel toChannel1 = toFile.getChannel();

        long position = 0;
        long count = sourceChannel.size();
        //参数position表示从position处开始向目标文件写入数据，count表示最多传输的字节数。
        // 如果源通道的剩余空间小于 count 个字节，则所传输的字节数要小于请求的字节数。
        //此外要注意，在SoketChannel的实现中，SocketChannel只会传输此刻准备好的数据（可能不足count字节）。
        // 因此，SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中
        toChannel1.transferFrom(sourceChannel, position, count);


        //2 transfer to  从 FileChanel 传到其他channel
        RandomAccessFile file2 = new RandomAccessFile("fromFile.txt", "rw");
        FileChannel fileChanel = file2.getChannel();

        RandomAccessFile toFile2 = new RandomAccessFile("toFile.txt", "rw");
        FileChannel toChannel2 = toFile.getChannel();
        long position2 = 0;
        long count2 = sourceChannel.size();
        fileChanel.transferTo(position2, count2, toChannel2);


    }


    /**
     * buffer 数据的类型，
     * 可以通过buffer.asReadOnlyBuffer 转换为只读buffer
     */
    @Test
    public void test25() throws IOException {
        //1，放入是什么类型，取出就按什么类型取 否则会抛出 bufferUnderFlowException
        ByteBuffer buf=ByteBuffer.allocate(64);
        buf.putInt(100);
        buf.putLong(200);
        //buf.putChar('中');
        buf.putShort((short)4);

        //2,取出（先反转）
        buf.flip();
        System.out.println(buf.getInt());
        System.out.println(buf.getLong());
        System.out.println(buf.getChar());
        System.out.println(buf.getShort());

    }


    /**
     * mappedByteBuffer （堆外内存）(其是一个抽象类，实际类型 是  DirectByteBuffer)
     * 1，
     * 可以让文件直接在内存中进行修改（操作系统不需要再拷贝一次）
     */
    @Test
    public void test26() throws IOException {
        RandomAccessFile file=new RandomAccessFile("d:/a.txt","rw");
        FileChannel channel=file.getChannel();
        //param1 表示使用的是读写模式，param2：表示可以直接修改的起始位置，param3 表示映射到内存的大小（单位字节）即将
        //文件的多少个字节映射的内存，下面的参数0,5 表示从0开始映射5 个字节
        MappedByteBuffer mappedByteBuffer=channel.map(FileChannel.MapMode.READ_WRITE,0,5);
        mappedByteBuffer.put(0,(byte)'a');
        mappedByteBuffer.put(1,(byte)'b');
        mappedByteBuffer.put(2,(byte)'c');
        mappedByteBuffer.put(3,(byte)'d');
        mappedByteBuffer.put(4,(byte)'e');

        file.close();

    }






    /**
     * buffer scatter,gather
     * 分散（scatter）从Channel中读取是指在读操作时将读取的数据写入多个buffer中。
     * 因此，Channel将从Channel中读取的数据“分散（scatter）”到多个Buffer中。
     * 聚集（gather）写入Channel是指在写操作时将多个buffer的数据写入同一个Channel，
     * 因此，Channel 将多个Buffer中的数据“聚集（gather）”后发送到Channel。
     * scatter / gather经常用于需要将传输的数据分开处理的场合，例如传输一个由消息头和消息体组成的消息，
     * 你可能会将消息体和消息头分散到不同的buffer中，这样你可以方便的处理消息头和消息体
     */
    @Test
    public void test3() {
        //1 分散,注意buffer首先被插入到数组，然后再将数组作为channel.read() 的输入参数。
        // read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer，
        // 当一个buffer被写满后，channel紧接着向另一个buffer中写（适合处理数据大小固定的数据）
        ByteBuffer header = ByteBuffer.allocate(128);
        ByteBuffer body = ByteBuffer.allocate(1024);
        ByteBuffer[] bufferArray = {header, body};
        //channel.read(bufferArray);

        //2,聚集，多个buffer channel,和 分散不同，实际数据有多少就写多少，适合处理 动态数据（数据大小不固定）
        ByteBuffer header1 = ByteBuffer.allocate(128);
        ByteBuffer body1 = ByteBuffer.allocate(1024);
        //write data into buffers
        ByteBuffer[] bufferArray1 = {header, body};
        //channel.write(bufferArray1);
    }

    /**
     * JAVA netty.test2NIO.nio  path 和file 类似
     */
    @Test
    public void test4() {

    }


    /**
     * selector
     * 与Selector一起使用时，Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用，
     * 因为FileChannel不能切换到非阻塞模式。而套接字通道都可以
     */
    @Test
    public void test6() throws IOException {
        Selector selector = Selector.open();
        //channel.configureBlocking(false);
        //当向Selector注册Channel时，register()方法会返回一个SelectionKey对象
        //SelectionKey selectionKey = channel.register(selector, Selectionkey.OP_READ);
        //如果你对不止一种事件感兴趣，那么可以用“位或”操作符将常量连接起来，如下：
        int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

        // 通过这个 selectionKey 可以获取到你感兴趣的事件集合

        //1,用“位与”interestSet 集合和给定的SelectionKey常量，可以确定某个确定的事件是否在interest 集合中
        // int interestSet = selectionKey.interestOps();

        /* boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT；
        boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
        boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
        boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;*/

        //2, readySet 集合是通道已经准备就绪的操作的集合
        // int readySet = selectionKey.readyOps();
        // 可以用像检测interest集合那样的方法，来检测channel中什么事件或操作已经就绪。
        // 但是，也可以使用以下四个方法，它们都会返回一个布尔类型
        /*selectionKey.isAcceptable();
        selectionKey.isConnectable();
        selectionKey.isReadable();
        selectionKey.isWritable();*/

        //3,从返回的 selectionKey 中获取  channel 和selector
        /*Channel  channel  = selectionKey.channel();
        Selector selector = selectionKey.selector();*/

        //4, 可以往 返回的 selectionKey中附加对象，这样就能方便的识别某个给定的通道
        /* selectionKey.attach(theObject);
        Object attachedObj = selectionKey.attachment();*/
        //也可以在 往channel中注册 的时候附加对象
        //SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

        //5, 通过select（）或select(timeout) 方法返回就绪的通道，select()方法返回的int值表示有多少通道已经就绪。
        // 亦即，自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法，
        // 因为有一个通道变成就绪状态，返回了1，若再次调用select()方法，如果另一个通道就绪了，
        // 它会再次返回1。如果对第一个就绪的channel没有做任何操作，现在就有两个就绪的通道，
        // 但在每次select()方法调用之间，只有一个通道就绪了
        int ready = selector.select(1000);

        //6,一旦调用了select()方法，并且返回值表明有一个或更多个通道就绪了，
        // 然后可以通过调用selector的selectedKeys()方法，
        // 访问“已选择键集（selected key set）”中的就绪通道
        Set<SelectionKey> selectedKeys = selector.selectedKeys();

        //当像Selector注册Channel时，Channel.register()方法会返回一个SelectionKey 对象。
        // 这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()
        // 方法访问这些对象(每注册一个channel返回一个，当注册多个是，就返回一个集合，可以遍历这个集合)
        //这个循环遍历已选择键集中的每个键，并检测各个键所对应的通道的就绪事件。
        //注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除
        // SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时，Selector会再次将其放入已选择键集中
        Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
        while (keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
            if (key.isAcceptable()) {
                // a connection was accepted by a ServerSocketChannel.
            } else if (key.isConnectable()) {
                // a connection was established with a remote server.
            } else if (key.isReadable()) {
                // a channel is ready for reading
            } else if (key.isWritable()) {
                // a channel is ready for writing
            }
            keyIterator.remove();
        }

        //8,某个线程调用select()方法后阻塞了，即使没有通道已经就绪，也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。
        //如果有其它线程调用了wakeup()方法，但当前没有线程阻塞在select()方法上，下个调用select()方法的线程会立即“醒来（wake up）”。
        selector.wakeup();

        //用完Selector后调用其close()方法会关闭该Selector，且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。
        selector.close();


    }





    /**
     * 1，SocketChannel 读
     */
    @Test
    public void test9() {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("http://www.baidu.com", 80));
            //从sockekchannel 读取数据
            ByteBuffer buf = ByteBuffer.allocate(2048);
            //len表示读到了多少字节，-1 表示读到了末尾，链接关闭了
            int len = socketChannel.read(buf);
            socketChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 2，从socketChannel 写
     */
    @Test
    public void test10() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("http://www.baidu.com", 80));
        String newData = "New String to write to file..." + System.currentTimeMillis();
        ByteBuffer buf = ByteBuffer.allocate(48);
        buf.clear();
        buf.put(newData.getBytes());
        buf.flip();
        //注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。
        // 所以，我们重复调用write()直到Buffer没有要写的字节为止
        while (buf.hasRemaining()) {
            socketChannel.write(buf);
        }

    }


    /**
     * socketChannel 非阻塞模式
     */
    @Test
    public void test11() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        //配置费阻塞模式
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("http://www.baidu.com", 80));
        //由于是非阻塞模式，connet 有可能实际上并没有连接上，所以这里要检查，有没有连接上
        ByteBuffer buf = ByteBuffer.allocate(48);
        while (!socketChannel.finishConnect()) {
            //非阻塞模式下，write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()
            //同理  非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值，它会告诉你读取了多少字节
            while (buf.hasRemaining()) {
                socketChannel.write(buf);
            }
        }

    }

    /**
     * serverSocketChannel 阻塞模式
     */
    @Test
    public void test12() {
        ServerSocketChannel serverSocketChannel = null;
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));
            while (true) {
                //默认情况下会阻塞在 accept 方法上，直到有连接进来
                SocketChannel socketChannel = serverSocketChannel.accept();
                //do something with socketChannel...
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                serverSocketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * serverSocketChannel 非阻塞模式
     */
    @Test
    public void test13() {
        ServerSocketChannel serverSocketChannel = null;
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(9999));
            serverSocketChannel.configureBlocking(false);
            while (true) {
                //非阻塞模式下 accept 会立即返回，所以得到的 socketChannel 有可能是null,所以要检查null
                SocketChannel socketChannel = serverSocketChannel.accept();
                if (socketChannel != null) {
                    //do something with socketChannel...
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。
     * 数据会被写到sink通道，从source通道读取。
     */
    @Test
    public void test14() {
        Pipe pipe = null;
        ExecutorService exec = Executors.newFixedThreadPool(2);
        try {
            pipe = Pipe.open();
            final Pipe pipeTemp = pipe;
            exec.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    Pipe.SinkChannel sinkChannel = pipeTemp.sink();//向通道中写数据
                    while (true) {
                        TimeUnit.SECONDS.sleep(1);
                        String newData = "Pipe Test At Time " + System.currentTimeMillis();
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        buf.clear();
                        buf.put(newData.getBytes());
                        buf.flip();
                        while (buf.hasRemaining()) {
                            System.out.println(buf);
                            sinkChannel.write(buf);
                        }
                    }
                }
            });

            exec.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    Pipe.SourceChannel sourceChannel = pipeTemp.source();//向通道中读数据
                    while (true) {
                        TimeUnit.SECONDS.sleep(1);
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        buf.clear();
                        int bytesRead = sourceChannel.read(buf);
                        System.out.println("bytesRead=" + bytesRead);
                        while (bytesRead > 0) {
                            buf.flip();
                            byte b[] = new byte[bytesRead];
                            int i = 0;
                            while (buf.hasRemaining()) {
                                b[i] = buf.get();
                                System.out.printf("%X", b[i]);
                                i++;
                            }
                            String s = new String(b);
                            System.out.println("=================||" + s);
                            bytesRead = sourceChannel.read(buf);
                        }
                    }
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            exec.shutdown();
        }
    }

    /**
     * DatagramChannel
     */
    @Test
    public void test15() {

    }


    /**
     * 1，
     * JAVA处理大文件，一般用BufferedReader,BufferedInputStream这类带缓冲的IO类，
     * 不过如果文件超大的话，更快的方式是采用MappedByteBuffer。
     * MappedByteBuffer是NIO引入的文件内存映射方案，读写性能极高。NIO最主要的就是实现了
     * 对异步操作的支持。其中一种通过把一个套接字通道(SocketChannel)注册到一个选择器(Selector)中,
     * 不时调用后者的选择(select)方法就能返回满足的选择键(SelectionKey),键中包含了SOCKET事件信息。这就是select模型。
     * SocketChannel的读写是通过一个类叫ByteBuffer来操作的.这个类本身的设计是不错的,比直接操作byte[]方便多了.
     * ByteBuffer有两种模式:直接/间接.间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即操作堆内存 (byte[]).
     * 但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用”直接”模式,
     * 即 MappedByteBuffer,文件映射.
     * 先中断一下,谈谈操作系统的内存管理.一般操作系统的内存分两部分:物理内存;虚拟内存.虚拟内存一般使用的
     * 是页面映像文件,即硬盘中的某个(某些)特殊的文件.操作系统负责页面文件内容的读写,这个过程叫”页面中断/切换”.
     * MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer.MappedByteBuffer
     * 只是一种特殊的ByteBuffer，即是ByteBuffer的子类。 MappedByteBuffer 将文件直接映射到内存
     * （这里的内存指的是虚拟内存，并不是物理内存）。通常，可以映射整个文件，如果文件比较大的话可以分段进行映射，
     * 只要指定文件的那个部分就可以
     * <p>
     * 2，
     * <p>
     * FileChannel提供了map方法来把文件影射为内存映像文件： MappedByteBuffer map(int mode,long position,long size);
     * 可以把文件的从position开始的size大小的区域映射为内存映像文件，mode指出了 可访问该内存映像文件的方式：
     * READ_ONLY,（只读）： 试图修改得到的缓冲区将导致抛出 ReadOnlyBufferException.(MapMode.READ_ONLY)
     * READ_WRITE（读/写）： 对得到的缓冲区的更改最终将传播到文件；该更改对映射到同一文件的其他程序不一定是可见的。
     * (MapMode.READ_WRITE)
     * PRIVATE（专用）： 对得到的缓冲区的更改不会传播到文件，并且该更改对映射到同一文件的其他程序也不是可见的；
     * 相反，会创建缓冲区已修改部分的专用副本。 (MapMode.PRIVATE)
     * MappedByteBuffer是ByteBuffer的子类，其扩充了三个方法：
     * force()：缓冲区是READ_WRITE模式下，此方法对缓冲区内容的修改强行写入文件；
     * load()：将缓冲区的内容载入内存，并返回该缓冲区的引用；
     * isLoaded()：如果缓冲区的内容在物理内存中，则返回真，否则返回假；
     * <p>
     * <p>
     * 下面是通过ByteBuffer 来读取文件  test21 是通过mappedBytteBuffer 来读取的。可以看到明显的效率差别
     */
    @Test
    public void test20() {
        RandomAccessFile aFile = null;
        FileChannel fc = null;
        try {
            aFile = new RandomAccessFile("src/1.ppt", "rw");
            fc = aFile.getChannel();
            long timeBegin = System.currentTimeMillis();
            ByteBuffer buff = ByteBuffer.allocate((int) aFile.length());
            buff.clear();
            fc.read(buff);
            //System.out.println((char)buff.get((int)(aFile.length()/2-1)));
            //System.out.println((char)buff.get((int)(aFile.length()/2)));
            //System.out.println((char)buff.get((int)(aFile.length()/2)+1));
            long timeEnd = System.currentTimeMillis();
            System.out.println("Read time: " + (timeEnd - timeBegin) + "ms");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (aFile != null) {
                    aFile.close();
                }
                if (fc != null) {
                    fc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 和上面的 test20 有明显的效率差别，更快
     */
    @Test
    public void test21() {
        RandomAccessFile aFile = null;
        FileChannel fc = null;
        try {
            aFile = new RandomAccessFile("src/1.ppt", "rw");
            fc = aFile.getChannel();
            long timeBegin = System.currentTimeMillis();
            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, aFile.length());
            // System.out.println((char)mbb.get((int)(aFile.length()/2-1)));
            // System.out.println((char)mbb.get((int)(aFile.length()/2)));
            //System.out.println((char)mbb.get((int)(aFile.length()/2)+1));
            long timeEnd = System.currentTimeMillis();
            System.out.println("Read time: " + (timeEnd - timeBegin) + "ms");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (aFile != null) {
                    aFile.close();
                }
                if (fc != null) {
                    fc.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
